/**
 * Copyright © 2011 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.creativor.rayson.transport.common;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * The Class Packet.
 */
public class Packet
{
	/**
	 * 
	 */
	private static final String CONTENT_LENGTH_TITLE = "Content-Length";
	private static final int MAX_CONTENT_LENGTH = 1024 * 1024;
	public static final byte CR = '\r';
	private final static byte[] HEADER_SPLITOR = new byte[] { ':', ' ' };
	public static final byte LF = '\n';
	private byte[] data;
	private int dataLength;
	private Map<String, String> headers;
	private String methodLine;
	
	/**
	 * Instantiates a new packet.
	 * 
	 * @param methodLine
	 *            the data
	 */
	public Packet()
	{
		headers = new HashMap<String, String>();
	}
	
	/**
	 * @param headerLine
	 * @throws PacketException
	 */
	private void addHeader(byte[] headerLine) throws PacketException
	{
		if (headerLine == null || headerLine.length <= 3)
			throw new PacketException("headerLine should not be null or length less than 3");
		// String headerString = new String(headerLine, 0, headerLine.length -
		// 1);
		int titleIndex = 0;// = headerString.indexOf(": ");
		for (int i = 0; i < headerLine.length; i++)
		{
			if (headerLine[i] == HEADER_SPLITOR[0] && headerLine[i + 1] == HEADER_SPLITOR[1])
				titleIndex = i;
		}
		String title = new String(Arrays.copyOfRange(headerLine, 0, titleIndex));
		String content =
				new String(Arrays.copyOfRange(headerLine, titleIndex + 2, headerLine.length - 2));
		this.headers.put(title, content);
		// analyze the header.
		if (CONTENT_LENGTH_TITLE.equals(title))
		{
			this.dataLength = Integer.valueOf(content);
			if (this.dataLength > MAX_CONTENT_LENGTH)
				throw new PacketException(CONTENT_LENGTH_TITLE + " exceed max length:"
						+ MAX_CONTENT_LENGTH);
		}
	}
	
	/**
	 * Gets the data.
	 * 
	 * @return the data
	 */
	public byte[] getData()
	{
		return data;
	}
	
	/**
	 * Gets the data length.
	 * 
	 * @return the data length
	 */
	public int getDataLength()
	{
		return dataLength;
	}
	
	/**
	 * @return the methodLine
	 */
	public String getMethodLine()
	{
		return methodLine;
	}
	
	private byte[] nextLine(byte[] src, int startIndex)
	{
		int searchIndex = startIndex;
		for (int i = searchIndex; i < src.length; i++)
		{
			if (src[searchIndex] == CR && src[searchIndex + 1] == LF)
			{
				byte[] result = new byte[searchIndex + 2 - startIndex];
				System.arraycopy(src, startIndex, result, 0, result.length);
				return result;
			} else
				searchIndex++;
		}
		throw new IllegalStateException();
	}
	
	/**
	 * Sets the data.
	 * 
	 * @param data
	 *            the new data
	 */
	void setData(byte[] data)
	{
		this.data = data;
	}
	
	private void setMethod(byte[] methodLine) throws PacketException
	{
		if (methodLine == null || methodLine.length <= 3)
			throw new PacketException("Method line should not be null or length less than 3");
		this.methodLine = new String(methodLine);
	}
	
	/**
	 * @param requestHeader
	 * @throws PacketException
	 */
	void setRequestHeader(byte[] requestHeader) throws PacketException
	{
		int searchIndex = 0;
		byte[] methodLine = nextLine(requestHeader, searchIndex);
		setMethod(methodLine);
		searchIndex += methodLine.length;
		while (searchIndex < requestHeader.length)
		{
			byte[] headerLine = nextLine(requestHeader, searchIndex);
			if (headerLine.length <= 2) break;
			addHeader(headerLine);
			searchIndex += headerLine.length;
		}
	}
	
	/**
	 * To string.
	 * 
	 * @return the string
	 */
	@Override
	public String toString()
	{
		StringBuffer sb = new StringBuffer();
		sb.append("{");
		sb.append("method line: ");
		sb.append(methodLine);
		sb.append(", headers: ");
		sb.append(headers);
		sb.append(", Data length: ");
		sb.append(dataLength);
		sb.append(", Data: ");
		sb.append(data == null ? "null" : new String(data));
		sb.append("}");
		return sb.toString();
	}
}
